/*******************************************************************************
 * Copyright (c) 2012-2015 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/

package org.eclipse.che.jdt.javaeditor;

import com.google.inject.Singleton;

import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.ide.ext.java.shared.dto.HighlightedPosition;
import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;

import java.util.ArrayList;
import java.util.List;

/**
 * Semantic highlighting reconciler
 *
 * @author Evgen Vidolob
 */
@Singleton
public class SemanticHighlightingReconciler {

    /**
     * Semantic highlightings
     */
    private SemanticHighlighting[] fJobSemanticHighlightings;

    /** Background job's added highlighted positions */
    private ThreadLocal<List<HighlightedPosition>> fAddedPositions = new ThreadLocal<>();

    private SemanticHighlighting fJobDeprecatedMemberHighlighting;

    public SemanticHighlightingReconciler() {
        fJobSemanticHighlightings = SemanticHighlightings.getSemanticHighlightings();
        for (SemanticHighlighting highlighting : fJobSemanticHighlightings) {
            if (highlighting instanceof SemanticHighlightings.DeprecatedMemberHighlighting) {
                fJobDeprecatedMemberHighlighting = highlighting;
                break;
            }
        }
    }

    public List<HighlightedPosition> reconcileSemanticHighlight(CompilationUnit ast) {
        fAddedPositions.set(new ArrayList<HighlightedPosition>());
        PositionCollector collector = new PositionCollector();
        ast.accept(collector);
        return fAddedPositions.get();
    }

    /**
     * Collects positions from the AST.
     */
    private class PositionCollector extends GenericVisitor {

        /** The semantic token */
        private SemanticToken fToken = new SemanticToken();

        /*
         * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visitNode(org.eclipse.jdt.core.dom.ASTNode)
         */
        @Override
        protected boolean visitNode(ASTNode node) {
            if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) {
//                retainPositions(node.getStartPosition(), node.getLength());
                return false;
            }
            return true;
        }

        /*
         * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.BooleanLiteral)
         */
        @Override
        public boolean visit(BooleanLiteral node) {
            return visitLiteral(node);
        }

        /*
         * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.CharacterLiteral)
         */
        @Override
        public boolean visit(CharacterLiteral node) {
            return visitLiteral(node);
        }

        /*
         * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.NumberLiteral)
         */
        @Override
        public boolean visit(NumberLiteral node) {
            return visitLiteral(node);
        }

        private boolean visitLiteral(Expression node) {
            fToken.update(node);
            for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) {
                SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i];
                if (semanticHighlighting.consumesLiteral(fToken)) {
                    int offset = node.getStartPosition();
                    int length = node.getLength();
                    if (offset > -1 && length > 0)
                        addPosition(offset, length, semanticHighlighting);
                    break;
                }
            }
            fToken.clear();
            return false;
        }

        /*
         * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.jdt.core.dom.ConstructorInvocation)
         * @since 3.5
         */
        @Override
        public boolean visit(ConstructorInvocation node) {
            // XXX Hack for performance reasons (should loop over fJobSemanticHighlightings can call consumes(*))
            if (fJobDeprecatedMemberHighlighting != null) {
                IMethodBinding constructorBinding = node.resolveConstructorBinding();
                if (constructorBinding != null && constructorBinding.isDeprecated()) {
                    int offset = node.getStartPosition();
                    int length = 4;
                    if (offset > -1 && length > 0)
                        addPosition(offset, length, fJobDeprecatedMemberHighlighting);
                }
            }
            return true;
        }

        /*
         * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.jdt.core.dom.ConstructorInvocation)
         * @since 3.5
         */
        @Override
        public boolean visit(SuperConstructorInvocation node) {
            // XXX Hack for performance reasons (should loop over fJobSemanticHighlightings can call consumes(*))
            if (fJobDeprecatedMemberHighlighting != null) {
                IMethodBinding constructorBinding = node.resolveConstructorBinding();
                if (constructorBinding != null && constructorBinding.isDeprecated()) {
                    int offset = node.getStartPosition();
                    int length = 5;
                    if (offset > -1 && length > 0)
                        addPosition(offset, length, fJobDeprecatedMemberHighlighting);
                }
            }
            return true;
        }

        /*
         * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SimpleName)
         */
        @Override
        public boolean visit(SimpleName node) {
            fToken.update(node);
            for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) {
                SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i];
                if (semanticHighlighting.consumes(fToken)) {
                    int offset = node.getStartPosition();
                    int length = node.getLength();
                    if (offset > -1 && length > 0)
                        addPosition(offset, length, semanticHighlighting);
                    break;
                }
            }
            fToken.clear();
            return false;
        }

        /**
         * Add a position with the given range and highlighting iff it does not exist already.
         *
         * @param offset
         *         The range offset
         * @param length
         *         The range length
         * @param highlighting
         *         The highlighting
         */
        private void addPosition(int offset, int length, SemanticHighlighting highlighting) {
            HighlightedPosition highlightedPosition = DtoFactory.getInstance().createDto(HighlightedPosition.class);
            highlightedPosition.setLength(length);
            highlightedPosition.setOffset(offset);
            highlightedPosition.setType(highlighting.getType());
            fAddedPositions.get().add(highlightedPosition);
        }

    }
}
